﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace QuickTuner.RadioProtocol
{
    public class ProtocolParser
    {
        // simple 1 to 10 characters for module and action
        private const string ProtocolPattern =
            @"radio://(?<module>[a-z]{1,10})/(?<action>[a-z]{1,10})" +
            @"(?<params>\?.+)?";

        private readonly Regex _urlParser;

        public ProtocolParser()
        {
            _urlParser = new Regex(ProtocolPattern);
        }


        // Methods ______________________________________________________________________

        public ProtocolAction Parse(string url)
        {
            var protocolAction = ParseModule(url);
            if (protocolAction == null) return null;

            if (!string.IsNullOrEmpty(protocolAction.ParameterString))
                protocolAction.Parameters = ParseParams(protocolAction.ParameterString);

            return protocolAction;
        }

        /// <summary>
        /// Extract module name and action name form the supplied url.
        /// However, this will not parse any parameters name-value and only
        /// return the whole parameter string.
        /// </summary>
        public ProtocolAction ParseModule(string url)
        {
            var match = _urlParser.Match(url);

            if (!match.Success)
                return null;

            return new ProtocolAction {
                Module = match.Groups["module"].Value,
                Action = match.Groups["action"].Value,
                ParameterString = match.Groups["params"].Value
            };
        }

        /// <summary>
        /// Split parameter string into name-value pairs.
        /// </summary>
        /// <remarks>
        /// Algorithm extracted from HttpUtility.ParseQueryString via Reflector.
        /// Re-implemented as to remove the dependence on System.Web
        /// </remarks>
        public IDictionary<string, string> ParseParams(string paramsString)
        {
            if (string.IsNullOrEmpty(paramsString))
                throw new ArgumentNullException("paramsString");

            // convert to dictionary
            var dict = new Dictionary<string, string>();

            // remove the leading ?
            if (paramsString.StartsWith("?"))
                paramsString = paramsString.Substring(1);

            var length = paramsString.Length;

            for (var i = 0; i < length; i++) {
                var startIndex = i;
                var pivotIndex = -1;

                while (i < length) {
                    char ch = paramsString[i];
                    if (ch == '=') {
                        if (pivotIndex < 0) {
                            pivotIndex = i;
                        }
                    } else if (ch == '&') {
                        break;
                    }
                    i++;
                }

                string name;
                string value;
                if (pivotIndex >= 0) {
                    name = paramsString.Substring(startIndex, pivotIndex - startIndex);
                    value = paramsString.Substring(pivotIndex + 1, (i - pivotIndex) - 1);
                } else {
                    name = paramsString.Substring(startIndex, i - startIndex);
                    value = null;
                }

                dict.Add(UrlDecode(name), UrlDecode(value));

                // if string ends with ampersand, add another empty token
                if ((i == (length - 1)) && (paramsString[i] == '&'))
                    dict.Add(null, string.Empty);
            }

            return dict;
        }

        /// <summary>
        /// Decodes an encoded url string.
        /// </summary>
        /// <remarks>
        /// Algorithm extracted from HttpValueCollection.FillFromString via Reflector.
        /// Re-implemented as to remove the dependence on System.Web
        /// </remarks>
        protected string UrlDecode(string url)
        {
            if (string.IsNullOrEmpty(url)) return url;

            int length = url.Length;
            var sb = new StringBuilder(url.Length);

            for (int i = 0; i < length; i++) {
                char ch = url[i];

                // + translates to a space
                if (ch == '+') {
                    sb.Append(' ');

                    // encoded parameter?
                } else if ((ch == '%') && (i < (length - 2))) {
                    // unicode literal?
                    if ((url[i + 1] == 'u') && (i < (length - 5))) {
                        int d0 = HexToInt(url[i + 2]);
                        int d1 = HexToInt(url[i + 3]);
                        int d2 = HexToInt(url[i + 4]);
                        int d3 = HexToInt(url[i + 5]);

                        if (((d0 < 0) || (d1 < 0)) || ((d2 < 0) || (d3 < 0))) {
                            if ((ch & 0xff80) == 0) {
                                sb.Append((byte)ch);
                            } else {
                                sb.Append(ch);
                            }
                        } else {
                            ch = (char)((((d0 << 12) | (d1 << 8)) | (d2 << 4)) | d3);
                            i += 5;
                            sb.Append(ch);
                        }
                    } else {
                        // hex-encoded?
                        int d0 = HexToInt(url[i + 1]);
                        int d1 = HexToInt(url[i + 2]);
                        if ((d0 >= 0) && (d1 >= 0)) {
                            var b = (byte)((d0 << 4) | d1);
                            i += 2;

                            sb.Append((char)b);
                        }
                    }
                } else {
                    sb.Append(ch);
                }
            }

            return sb.ToString();
        }

        // helper for url decode
        protected int HexToInt(char hexDigit)
        {
            if ((hexDigit >= '0') && (hexDigit <= '9')) {
                return (hexDigit - '0');
            }
            if ((hexDigit >= 'a') && (hexDigit <= 'f')) {
                return ((hexDigit - 'a') + 10);
            }
            if ((hexDigit >= 'A') && (hexDigit <= 'F')) {
                return ((hexDigit - 'A') + 10);
            }
            return -1;
        }
    }
}